﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Xsl;
using System.Xml;
using System.IO;
using Dixon.JSClientPages;

namespace Dixon.Jscp
{
    /// <summary>
    /// Represents a compiled JavaScript Client Page
    /// </summary>
    public sealed class JSClientPage
    {
        private static IDictionary<string, XslCompiledTransform> compiledStylesheets
            = new Dictionary<string, XslCompiledTransform>();

        /// <summary>
        /// The configuration of this page
        /// </summary>
        public Configuration Configuration { get; private set; }

        /// <summary>
        /// The name of the source file, relative to the directory
        /// where JSCP pages are stored on the Web site
        /// </summary>
        public string Source { get; private set; }

        /// <summary>
        /// The full path to the source page
        /// </summary>
        public string SourcePath { get; private set; }

        /// <summary>
        /// The full path to the compiled page
        /// </summary>
        public string CompiledPath { get; private set; }

        /// <summary>
        /// Creates a new JavaScript Client Page instance
        /// </summary>
        /// <param name="configuration">
        /// The configuration to be used (to locate source pages and compiled pages)
        /// </param>
        /// <param name="source">
        /// The name of the source file 
        /// </param>
        public JSClientPage(Configuration configuration, string source)
        {
            Configuration = configuration;
            Source = source;
            SourcePath = Configuration.GetSourcePath(source);
            CompiledPath = Configuration.GetCompiledPath(source);
        }


        /// <summary>
        /// Determines whether the page has ever been compiled.
        /// </summary>
        public bool IsCompiled
        {
            get
            {
                return File.Exists(CompiledPath);
            }
        }

        /// <summary>
        /// Indicates whether or not the source file has been modified since
        /// the last time the page was compiled.
        /// </summary>
        /// <remarks>
        /// If the file has never been compiled, this method will also return true
        /// </remarks>
        public bool IsModified
        {
            get
            {
                bool modified;
                if (IsCompiled)
                {
                    var sourceWriteTime = File.GetLastWriteTime(SourcePath);
                    var compiledWriteTime = File.GetLastWriteTime(CompiledPath);
                    modified = compiledWriteTime < sourceWriteTime;
                }
                else
                {
                    modified = true;
                }
                return modified;
            }
        }


        /// <summary>
        /// Compiles this page only if it has been modified since the 
        /// last time it was compiled
        /// </summary>
        public void Check()
        {
            if (IsModified) {
                Compile();
            }
        }


        /// <summary>
        /// Outputs the compiled JavaScript content of this
        /// page to an arbitrary stream using the specified 
        /// text encoding. The page is recompiled if necessary.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="encoding"></param>
        public void Output(Stream stream, Encoding encoding)
        {
            try
            {
                Check();
                using (var writer = new StreamWriter(stream, encoding))
                {
                    writer.Write(File.ReadAllText(CompiledPath));
                }
            }
            catch (Exception e)
            {
                var pageError = PageError.FromException(e);
                pageError.DisplayInScript(stream, encoding);
            }
        }



        /// <summary>
        /// Compiles or re-compiles this page
        /// </summary>
        public void Compile()
        {
            if (File.Exists(CompiledPath))
            {
                File.Delete(CompiledPath);
            }

            Configuration.MakeDirectory(Configuration.CompiledPagePath, Source);

            var stylesheet = 
                JSClientPage.GetCompiledStylesheet(
                Configuration.CompilerStylesheetPath);

            var inputSettings = new XmlReaderSettings();
            inputSettings.ProhibitDtd = false;
            using (var reader = XmlReader.Create(SourcePath, inputSettings))
            {
                using (var writer = File.CreateText(CompiledPath))
                {
                    stylesheet.Transform(
                        reader,
                        new XsltArgumentList(),
                        writer
                    );
                }
            }

        }


        /// <summary>
        /// Obtains a compiled stylesheet instance for the specified file. If the
        /// stylesheet in question has not been compiled yet, it will be compiled
        /// </summary>
        /// <param name="stylesheetFile">
        /// the path to the stylesheet file
        /// </param>
        /// <returns>a compiled stylesheet instance</returns>
        private static XslCompiledTransform GetCompiledStylesheet(string stylesheetFile) 
        {
            XslCompiledTransform stylesheet;

            if (compiledStylesheets.ContainsKey(stylesheetFile))
            {
                stylesheet = compiledStylesheets[stylesheetFile];
            }
            else
            {
                stylesheet = new XslCompiledTransform();
                var settings = new XsltSettings();
                settings.EnableDocumentFunction = true;
                stylesheet.Load(stylesheetFile, settings, new XmlUrlResolver());
                compiledStylesheets[stylesheetFile] = stylesheet;
                
            }
            return stylesheet;
        }

    }
}
